package net.sudot.chess.business.controller.websocket;

import net.sudot.chess.business.dto.ChatMessageDTO;
import net.sudot.chess.business.model.ChatRecord;
import net.sudot.chess.business.model.Customer;
import net.sudot.chess.business.model.Room;
import net.sudot.chess.business.model.RoomCustomerRef;
import net.sudot.chess.business.service.ChatRecordService;
import net.sudot.chess.business.service.CustomerService;
import net.sudot.chess.business.service.RoomCustomerRefService;
import net.sudot.chess.business.service.RoomService;
import net.sudot.chess.business.wechat.message.InformationTemplateMessage;
import net.sudot.chess.config.WebSocketGetHttpSessionConfigurator;
import net.sudot.chess.util.ObjectUtil;
import net.sudot.commons.json.JsonUtils;
import net.sudot.commons.security.LoginUser;
import net.sudot.commons.utils.ResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 聊天室WebSocket服务端
 *
 * @author tangjialin on 2018-03-10.
 */
@Controller
@ServerEndpoint(value = "/WebSocket/chat/{roomId}", configurator = WebSocketGetHttpSessionConfigurator.class)
public class ChatWebSocketServer {
    private static final Logger logger = LoggerFactory.getLogger(ChatWebSocketServer.class);
    private static final String ROOM_ID_KEY = "roomId";
    /** 同一个房间下所有的会话信息 */
    private static final Map<Long, Set<Session>> ROOM_AND_SESSION_MAP = new ConcurrentHashMap<>();

    @Resource
    private CustomerService customerService;
    @Resource
    private RoomService roomService;
    @Resource
    private RoomCustomerRefService roomCustomerRefService;
    @Resource
    private ChatRecordService chatRecordService;
    @Resource
    private InformationTemplateMessage informationTemplateMessage;

    /**
     * 连接建立成功调用的方法
     *
     * @param session Session
     * @param config  EndpointConfig
     * @param roomId  房间主键
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam(ROOM_ID_KEY) Long roomId) {
        // 加入set中
        Set<Session> sessions = ROOM_AND_SESSION_MAP.get(roomId);
        if (sessions == null) {
            synchronized (ROOM_AND_SESSION_MAP) {
                sessions = ROOM_AND_SESSION_MAP.get(roomId);
                if (sessions == null) {
                    sessions = new HashSet<>();
                    ROOM_AND_SESSION_MAP.put(roomId, sessions);
                }
            }
        }
        // 加入set中
        sessions.add(session);
        Customer customer = getCustomer(session);
        List<ChatMessageDTO> chatRecordDetailByRoom = chatRecordService.findListByRoomAtAfter(roomId, customer.getId());
        sendMessage(session, new ResponseMessage(true, 1, chatRecordDetailByRoom));
    }

    /**
     * 消息接收
     *
     * @param session Session
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void receiveMessage(Session session, String message) {
        Long roomId = getRoomId(session);
        Room room = roomService.find(roomId);
        if (room.getStatus() == Room.Status.END) {
            // 房间已关闭
            sendMessage(session, new ResponseMessage(false, 99));
            return;
        }
        Customer customer = getCustomer(session);
        ResponseMessage responseMessage = processMessage(customer, message);
        if (!responseMessage.isSuccess()) {
            sendMessage(session, responseMessage);
            return;
        }

        ChatMessageDTO messageDTO = (ChatMessageDTO) responseMessage.getData();
        String messageContent = messageDTO.getContent();
        if ("[结束]".equals(messageContent)) {
            if (room.getStatus() == Room.Status.PLAYING) {
                // 关闭房间
                try {
                    roomService.applyComplete(room, customer);
                    // 获取房间最新状态
                    room = roomService.find(roomId);
                    if (room.getStatus() == Room.Status.END) {
                        responseMessage.setCode(99);
                    }
                } catch (Exception e) {
                    logger.error("关闭房间发起异常:roomId:{} customerId:{}", roomId, customer.getId(), e);
                    sendMessage(session, new ResponseMessage(false, "结束游戏操作失败"));
                    return;
                }
            } else {
                sendMessage(session, new ResponseMessage(false, "游戏未开始,不能发起结束"));
                return;
            }
        } else if ("[房号]".equals(messageContent)) {
            if (customer.isGameMaster()) {
                responseMessage.setCode(2);
            } else if (room.getStatus() != Room.Status.PLAYING) {
                sendMessage(session, new ResponseMessage(false, "游戏未开始,不能发送房号"));
                return;
            }
        }

        // 存储信息
        store(customer, roomId, messageContent);
        // 发给房间所有人
        sendMessageAtRoom(roomId, responseMessage);
        // 房间已关闭,退出聊天室
        if (responseMessage.getCode() == 99) { return; }
        // 发送微信通知信息,不管其是否在线,都发送
        if ("[结束]".equals(messageContent)) {
            List<Customer> customerList = customerService.findListByRoom(roomId);
            Map<Long, Boolean> customerRefMap = roomCustomerRefService.findByRoom(roomId)
                    .stream().collect(Collectors.toMap(RoomCustomerRef::getCustomerId, RoomCustomerRef::getClosed));
            for (Customer player : customerList) {
                // 排除发送人自己
                if (customer.getId().equals(player.getId())) { continue; }
                // 排除已点击结束游戏的玩家
                if (Boolean.TRUE.equals(customerRefMap.get(player.getId()))) { continue; }
                informationTemplateMessage.send(player, messageDTO);
            }
        } else if (messageContent.contains("@")) {
            List<Customer> customerList = customerService.findListByRoom(roomId);
            for (Customer player : customerList) {
                // 排除发送人自己
                if (customer.getId().equals(player.getId())) { continue; }
                if (!messageContent.contains("@" + player.getNickname())) { continue; }
                informationTemplateMessage.send(player, messageDTO);
            }
        }
    }

    /**
     * 发送信息到前端
     *
     * @param session         Session
     * @param responseMessage 发送的信息 responseMessage.code:响应状态码(0:普通消息,1:所有信息集合,99:房间已关闭) responseMessage.data:ChatMessageDTO
     */
    private static void sendMessage(Session session, ResponseMessage responseMessage) {
        try {
            session.getBasicRemote().sendText(JsonUtils.toJson(responseMessage));
        } catch (Exception e) {
            logger.error("信息回复异常:{}", JsonUtils.toJson(responseMessage), e);
        }
    }

    /**
     * 发送信息给房间所有人
     *
     * @param roomId  发送的房间
     * @param message 发送的信息
     */
    private static void sendMessageAtRoom(Long roomId, ResponseMessage message) {
        // 发给房间所有人
        Set<Session> sessions = ROOM_AND_SESSION_MAP.get(roomId);
        if (sessions == null) { return; }
        Iterator<Session> sessionIterator = sessions.iterator();
        while (sessionIterator.hasNext()) {
            Session userSession = sessionIterator.next();
            if (!userSession.isOpen()) {
                sessionIterator.remove();
                continue;
            }
            try {
                sendMessage(userSession, message);
            } catch (Exception e) {
                logger.error("信息发送异常", e);
            }
        }
    }

    /**
     * 发送房间更新信息
     *
     * @param roomId   发送的房间
     * @param customer 发送人
     */
    public static void sendMessageAtRoomUpdate(Long roomId, Customer customer) {
        ChatMessageDTO chatMessage = new ChatMessageDTO();
        chatMessage.setContent("[房号]");
        chatMessage.setCustomerId(customer.getId());
        chatMessage.setHead(customer.getHead());
        chatMessage.setNickname(customer.getNickname());
        sendMessageAtRoom(roomId, new ResponseMessage(true, 2, chatMessage));
    }

    /**
     * 游戏玩家加入信息
     *
     * @param roomId 发送的房间
     */
    public static void sendMessageAtPlayerAdd(Long roomId) {
        sendMessageAtRoom(roomId, new ResponseMessage(true, 2));
    }

    /**
     * 游戏玩家退出信息
     *
     * @param roomId 发送的房间
     */
    public static void sendMessageAtPlayerExit(Long roomId) {
        sendMessageAtRoom(roomId, new ResponseMessage(true, 2));
    }

    /**
     * 连接关闭
     *
     * @param session Session
     */
    @OnClose
    public void onClose(Session session) {
        Customer customer = getCustomer(session);
        Long roomId = getRoomId(session);
        logger.info("WebSocket链接关闭:{} {}", customer.getId(), customer.getMobile());
        Set<Session> servers = ROOM_AND_SESSION_MAP.get(roomId);
        servers.remove(session);
    }

    /**
     * 链接异常
     *
     * @param session Session
     * @param error   异常信息
     */
    @OnError
    public void onError(Session session, Throwable error) {
//        logger.error("链接异常", error);
        onClose(session);
    }

    /**
     * 处理信息
     *
     * @param customer 发送信息的客户信息
     * @param message  JSON格式信息主体
     * @return 返回信息处理结果
     */
    public ResponseMessage processMessage(Customer customer, String message) {
        ResponseMessage responseMessage = new ResponseMessage();
        ChatMessageDTO chatMessage;
        try {
            chatMessage = JsonUtils.toObject(message, ChatMessageDTO.class);
        } catch (Exception e) {
            logger.warn("信息解析异常:{}", message, e);
            return responseMessage.setMessage("信息格式错误");
        }
        logger.info("收到客户端消息:{}", message);

        String content = chatMessage.getContent();
        if (ObjectUtil.isEmpty(content)) {
            return responseMessage.setMessage("信息内容不能为空");
        }

        chatMessage.setCustomerId(customer.getId());
        chatMessage.setHead(customer.getHead());
        chatMessage.setNickname(customer.getNickname());
        responseMessage.setSuccess(true).setData(chatMessage);
        return responseMessage;
    }

    public Long getRoomId(Session session) {
        return Long.parseLong(session.getPathParameters().get(ROOM_ID_KEY));
    }

    public Customer getCustomer(Session session) {
        return (Customer) session.getUserProperties().get(LoginUser.class.getSimpleName());
    }

    /**
     * 存储信息
     *
     * @param customer 发送的客户
     * @param roomId   信息所属房间
     * @param content  信息内容
     */
    public void store(Customer customer, Long roomId, String content) {
        ChatRecord chatRecord = new ChatRecord();
        // 此处是WebSocket协议里,net.sudot.chess.config.BaseEntityAuditing获取不到用户信息
        chatRecord.setCreatedBy(customer.getId());
        chatRecord.setLastModifiedBy(customer.getId());
        chatRecord.setRoomId(roomId);
        chatRecord.setContent(content);
        chatRecordService.save(chatRecord);
    }
}
